package datastruct.lock;

import java.util.Arrays;
import java.util.HashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Locks {

    private ReentrantReadWriteLock[] locks;

    public Locks(ReentrantReadWriteLock[] lock) {
        this.locks = lock;
    }

    public static Locks makeLock(int size) {
        ReentrantReadWriteLock[] lockArr = new ReentrantReadWriteLock[size];
        for (int i=0;i<size;i++) {
            lockArr[i] = new ReentrantReadWriteLock();
        }
        Locks locks = new Locks(lockArr);
        return locks;
    }

    public static long fnv32(String key) {
        long hash = 2166136261L;
        char [] ch = key.toCharArray();
        long prime32 = 16777619;
        for (int i=0;i<ch.length;i++) {
            hash *= prime32;
            hash ^= ch[i];
        }
        return hash;
    }

    private long spread(long hashCode) {
        long tableSize = locks.length;
        return  hashCode & (tableSize-1);
    }

    // 把传入的 key 数组转换成对应的 读写锁索引
    public long[] toLockIndices(String[] keys, boolean reverse) {
        if (keys == null) {
            return null;
        }
        HashMap<Long, Boolean> indexSet = new HashMap<>();
        for (String key : keys) {
            // 把 key 映射成 hashCode
            long hashCode = fnv32(key);
            // 再把 hashCode 映射成 locks 数组中的索引
            long index = spread(hashCode);
            // 等价于 把 key 映射成 locks 数组中的索引
            indexSet.put(index, true);
        }

        long[] indexSlice = new long[indexSet.size()];

        int i = 0;
        for (long k : indexSet.keySet()) {
            indexSlice[i] = k;
            i++;
        }

        Arrays.sort(indexSlice);
        return indexSlice;
    }

    public void rwLocks(String[] writeKeys, String[] readKeys) {
        long[] writeKeyIndex = toLockIndices(writeKeys, false);
        long[] readKeyIndex = toLockIndices(readKeys, false);
        HashMap<Long, Boolean> writeMap = new HashMap<>();
        if (writeKeyIndex != null) {
            for (long i : writeKeyIndex) {
                writeMap.put(i, true);
                int index = (int)i;
                locks[index].writeLock().lock();
            }
        }

        if (readKeyIndex != null) {
            for (long i : readKeyIndex) {
                int index = (int)i;
                if (writeMap.containsKey(i)) {
                    continue;
                }
                locks[index].readLock().lock();
            }
        }
    }

    public void rwUnLocks(String[] writeKeys, String[] readKeys) {
        long[] writeKeyIndex = toLockIndices(writeKeys, false);
        long[] readKeyIndex = toLockIndices(readKeys, false);
        HashMap<Long, Boolean> writeMap = new HashMap<>();
        if (writeKeyIndex != null) {
            for (long i : writeKeyIndex) {
                writeMap.put(i, true);
                int index = (int)i;
                locks[index].writeLock().unlock();
            }
        }

        if (readKeyIndex != null) {
            for (long i : readKeyIndex) {
                int index = (int)i;
                if (writeMap.containsKey(i)) {
                    continue;
                }
                locks[index].readLock().unlock();
            }
        }
    }
}
